/** @file
  @todo ADD DESCRIPTION

 @copyright
  INTEL CONFIDENTIAL
  Copyright 1999 - 2018 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains a 'Sample Driver' and is licensed as such under the terms
  of your license agreement with Intel or your vendor. This file may be modified
  by the user, subject to the additional terms of the license agreement.

@par Specification Reference:
**/
#include <Protocol/AcpiTable.h>
#include <IndustryStandard/Acpi.h>
#include <Protocol/AcpiSystemDescriptionTable.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BaseLib.h>
#include <SetupVariable.h>
#include <CommonDefinitions.h>
#include <Library/TbtCommonLib.h>
#include <TbtBoardInfo.h>
#include <Protocol/GlobalNvsArea.h>
#include <Protocol/FirmwareVolume2.h>
#include <Protocol/AcpiTable.h>
#include <IndustryStandard/Acpi10.h>
#include <Library/DebugLib.h>
#include <Library/HobLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/IoLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/CpuPlatformLib.h>
#include <Guid/HobList.h>
#include <Library/UefiLib.h>
#include <Uefi/UefiSpec.h>
#include <Library/PcdLib.h>
#include <Library/MmPciLib.h>
#include <Library/GpioExpanderLib.h>
#include <GpioPinsSklLp.h>
#include <CpuRegs.h>
#include <PcieRegs.h>
#include <Library/TbtCommonLib.h>
#include <Protocol/SaGlobalNvsArea.h>
#include <Protocol/GlobalNvsArea.h>
#include <Library/DxeTbtSecurityLib.h>

#define TBT_DEBUG_AREA_SIZE_IN_KB     64

GLOBAL_REMOVE_IF_UNREFERENCED TBT_INFO_HOB            *gTbtInfoHob;
GLOBAL_REMOVE_IF_UNREFERENCED SETUP_DATA              mSetupData;
GLOBAL_REMOVE_IF_UNREFERENCED EFI_PHYSICAL_ADDRESS    gTbtDebugBaseAddressMem = 0;
GLOBAL_REMOVE_IF_UNREFERENCED EFI_PHYSICAL_ADDRESS    gTbtDgpuPciBaseAddressMem;
GLOBAL_REMOVE_IF_UNREFERENCED EFI_PHYSICAL_ADDRESS    gTbtDeviceTreeBuffer;
GLOBAL_REMOVE_IF_UNREFERENCED VOID                    *VbiosAddress = NULL;
GLOBAL_REMOVE_IF_UNREFERENCED UINT32                  VbiosSize;
GLOBAL_REMOVE_IF_UNREFERENCED UINT8                   TbtDetachableGfxSupport = 0;
GLOBAL_REMOVE_IF_UNREFERENCED UINT8                   TbtRpSelector;
GLOBAL_REMOVE_IF_UNREFERENCED CPU_SKU                 CpuSku;



VOID
TbtCallback (
  IN EFI_EVENT    Event,
  IN VOID         *Context
  )
/*++
Routine Description:
   Issues an SwSmi to enumerate and assign resources for Tbt devices
Arguments:
  Event - Event that caused the call
  Context- Context at which event was caused
Returns:
  None
--*/
{
  EFI_GLOBAL_NVS_AREA_PROTOCOL  *GlobalNvsAreaProtocol;
  EFI_STATUS                    Status;
  UINTN                         DataSize;
  EFI_GLOBAL_NVS_AREA           *mGlobalNvsAreaPtr;
  SETUP_DATA                    SetupData;

  DataSize = sizeof (SETUP_DATA);

  SetMem (&SetupData, DataSize, 0);

  Status = gRT->GetVariable (
                  L"Setup",
                  &gSetupVariableGuid,
                  NULL,
                  &DataSize,
                  &SetupData
                  );
  if (!EFI_ERROR (Status) && SetupData.TbtSupport) {
    Status = gBS->LocateProtocol (&gEfiGlobalNvsAreaProtocolGuid, NULL, (VOID **) &GlobalNvsAreaProtocol);
    ASSERT_EFI_ERROR (Status);
    mGlobalNvsAreaPtr = GlobalNvsAreaProtocol->Area;
    GlobalNvsAreaProtocol->Area->ThunderboltHotSmi    = SetupData.TBTHotSMI;
    GlobalNvsAreaProtocol->Area->ThunderboltHotNotify = SetupData.TBTHotNotify;

    if (SetupData.CallSmiBeforeBoot & RESET_HR_BIT) {
      //
      // Reset PCIe tree under Host Router
      //
      GlobalNvsAreaProtocol->Area->ThunderboltSmiFunction = 22;
      IoWrite8 (SW_SMI_IO_ADDRESS, SW_SMI_TBT_ENUMERATE);
    }

    if (SetupData.CallSmiBeforeBoot & ENUMERATE_HR_BIT) {
      //
      // Call SMI handler to enumerate devices under Host Router properly
      //
      GlobalNvsAreaProtocol->Area->ThunderboltSmiFunction = 21;
      IoWrite8 (SW_SMI_IO_ADDRESS, SW_SMI_TBT_ENUMERATE);
    }
    if (SetupData.Multicontroller) {
      if (SetupData.CallSmiBeforeBoot & RESET_HR_BIT) {
      //
      // Reset PCIe tree under Host Router
      //
      GlobalNvsAreaProtocol->Area->ThunderboltSmiFunction = 25;
      IoWrite8 (SW_SMI_IO_ADDRESS, SW_SMI_TBT_ENUMERATE);
    }

    if (SetupData.CallSmiBeforeBoot & ENUMERATE_HR_BIT) {
      //
      // Call SMI handler to enumerate devices under Host Router properly
      //
      GlobalNvsAreaProtocol->Area->ThunderboltSmiFunction = 24;
      IoWrite8 (SW_SMI_IO_ADDRESS, SW_SMI_TBT_ENUMERATE);
    }
}
  }

  gBS->CloseEvent (Event);
}

/**
  GPIO write

  @param[in]  GpioAccessType
  @param[in]  Expander
  @param[in]  GpioNumber
  @param[in]  Value
**/
void
GpioWrite (
  IN  UINT8          GpioAccessType,
  IN  UINT8          Expander,
  IN  UINT32         GpioNumber,
  IN  BOOLEAN        Value
  )
{
  if (GpioAccessType == 0x01) {
    // PCH
    GpioSetOutputValue (GpioNumber, (UINT32)Value);
  } else if (GpioAccessType == 0x02) {
    // IoExpander {TCA6424A}

    GpioExpSetOutput(Expander, (UINT8)GpioNumber, (UINT8)Value);
  }
}

/**
  GPIO read

  @param[in]  GpioAccessType
  @param[in]  Expander
  @param[in]  GpioNumber
  @param[in]  PadState {0: GPO [GPIO TX State], 1: GPI [GPIO RX State]}
**/
UINT8
GpioRead (
  IN  UINT8          GpioAccessType,
  IN  UINT8          Expander,
  IN  UINT32         GpioNumber,
  IN  UINT8          PadState
  )
{
  UINT8       Data8;
  UINT32      Data32;

  Data8 = 0;
  Data32 = 0;

  if (GpioAccessType == 0x01) {
    // PCH
    if (PadState == 0x01) {
      // GPIO RX State
      GpioGetInputValue (GpioNumber, &Data32);
      Data8 = (UINT8) Data32;
    }
  } else if (GpioAccessType == 0x02) {
    // IoExpander {TCA6424A}

    Data8 = GpioExpGetInput (Expander, (UINT8)GpioNumber);
  }

  return Data8;
}

/**
  Based on the Security Mode Selection, BIOS drives FORCE_PWR.
**/
VOID
ForcePower(
  IN  UINT8          GpioAccessType,
  IN  UINT8          Expander,
  IN  UINT32         GpioNumber,
  IN  BOOLEAN        Value
)
{
  GpioWrite(GpioAccessType, Expander, GpioNumber, Value);
}

VOID
TbtSetPCIe2TBTCommand (
  IN    UINT8        command,
  IN    UINT8        TBT_US_BUS
  )
{
  volatile UINT32 REG_VAL;
  volatile UINT32 max_wait_Iter;
  volatile UINT8  RetCode;
  UINTN           DeviceBaseAddress;
  //
  // Wait 5 sec
  //
  max_wait_Iter     = 50;
  RetCode           = 0x52;

  DeviceBaseAddress = MmPciBase (TBT_US_BUS, 0x00, 0x00);
  MmioWrite32 (DeviceBaseAddress + PCIE2TBT_R, command | PCIE2TBT_VLD_B);

  IoWrite8 (0x80, 0x50);

  while (max_wait_Iter-- > 0) {
    REG_VAL = MmioRead32 (DeviceBaseAddress + TBT2PCIE_R);
    if (0xFFFFFFFF == REG_VAL) {
      //
      // Device is not here return now
      //
      RetCode = 0x5F;
      break;
    }

    if (REG_VAL & TBT2PCIE_DON_R) {
      RetCode = 0x51;
      break;
    }

    gBS->Stall (100 * 1000);
  }

  MmioWrite32 (DeviceBaseAddress + PCIE2TBT_R, 0);
  IoWrite8 (0x80, RetCode);
}

/**
  Initialize Thunderbolt(TM) SSDT ACPI tables

  @retval EFI_SUCCESS    ACPI tables are initialized successfully
  @retval EFI_NOT_FOUND  ACPI tables not found
**/
EFI_STATUS
EFIAPI
TbtDxeEntryPoint (
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  EFI_STATUS  Status;
  EFI_EVENT   Event;
  UINTN       DataSize;
  SA_SETUP    SaSetup;

  UINT32      SetupAttr;
  VOID        *MemoryBuffer;
  EFI_EVENT   ExitBootServiceEvent;
  EFI_EVENT   ReadyToLockEvent;
  EFI_EVENT   EndOfDxeEvent;
  EFI_GLOBAL_NVS_AREA_PROTOCOL  *GlobalNvsAreaProtocol;
  EFI_GLOBAL_NVS_AREA           *mGlobalNvsAreaPtr;

  Status = EFI_SUCCESS;
  ExitBootServiceEvent = NULL;
  ReadyToLockEvent = NULL;
  EndOfDxeEvent = NULL;

  CpuSku      = GetCpuSku ();
  Status = gBS->LocateProtocol (&gEfiGlobalNvsAreaProtocolGuid, NULL, (VOID **) &GlobalNvsAreaProtocol);
  ASSERT_EFI_ERROR (Status);
  mGlobalNvsAreaPtr = GlobalNvsAreaProtocol->Area;

  DEBUG ((DEBUG_INFO, "TbtDxeEntryPoint\n"));

  //
  // Get TBT INFO HOB
  //
  Status = EfiGetSystemConfigurationTable (&gEfiHobListGuid, (VOID **) &gTbtInfoHob);
  if (!EFI_ERROR (Status) && (gTbtInfoHob != NULL)) {
    gTbtInfoHob = GetNextGuidHob (&gTbtInfoHobGuid, gTbtInfoHob);
    if (gTbtInfoHob == NULL) {
      DEBUG ((DEBUG_INFO, "TBT HOB not found\n"));
      return EFI_NOT_FOUND;
    }
  } else {
    return EFI_NOT_FOUND;
  }


  DataSize = sizeof (mSetupData);
  Status = gRT->GetVariable (
                  L"Setup",
                  &gSetupVariableGuid,
                  &SetupAttr,
                  &DataSize,
                  &mSetupData
                  );

  if (EFI_ERROR (Status)) {
    SetupAttr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS;
  }
  if (mSetupData.TbtSupport && mSetupData.TbtSkipPciEnumeration) {
    PcdSetBoolS (PcdTbtSkipPciEnumeration, TRUE); // Skip bios boot time PCI enumeration for Tbt devices
    DEBUG ((DEBUG_INFO, "PcdTbtSkipPciEnumeration = TRUE\n"));
  } else {
    PcdSetBoolS (PcdTbtSkipPciEnumeration, FALSE); // Allow bios boot time PCI enumeration for Tbt devices
    DEBUG ((DEBUG_INFO, "PcdTbtSkipPciEnumeration = FALSE\n"));
  }

  if (!EFI_ERROR (Status) && mSetupData.TbtSupport) {
    //
    // Reserve TBT_DEBUG_AREA_SIZE_IN_KB buffer of system memory to store Tbt Debug data.
    //
    gTbtDebugBaseAddressMem = 0xFFFFFFFF;
    Status = gBS->AllocatePages (
                    AllocateMaxAddress,
                    EfiReservedMemoryType,
                    EFI_SIZE_TO_PAGES (TBT_DEBUG_AREA_SIZE_IN_KB*1024),
                    &gTbtDebugBaseAddressMem
                    );
    ASSERT_EFI_ERROR(Status);

    //
    //  Initialize TBT Debug Area to lower case 'x'
    //
    SetMem ((VOID *)(UINTN) gTbtDebugBaseAddressMem, TBT_DEBUG_AREA_SIZE_IN_KB*1024, 0x78);

    mSetupData.TbtDebugBaseAddressMem = (UINT32) gTbtDebugBaseAddressMem;
    DEBUG ((DEBUG_INFO, "<TbtDxe> TbtDebugBaseAddressMem = %lx \n", gTbtDebugBaseAddressMem));

    TbtDetachableGfxSupport = mSetupData.TbtDetachableGfxSupport;
    mGlobalNvsAreaPtr->TbtDetachableGfxSupport = mSetupData.TbtDetachableGfxSupport;
    DEBUG ((DEBUG_INFO, "<TbtDxe> TbtDetachableGfxSupport = %x \n", TbtDetachableGfxSupport));
    DEBUG ((DEBUG_INFO, "<TbtDxe> TbtDetachableGfxSupport = %x \n", mGlobalNvsAreaPtr->TbtDetachableGfxSupport));
    if(TbtDetachableGfxSupport) {
        gTbtDgpuPciBaseAddressMem = 0xFFFFFFFF;
        Status = (gBS->AllocatePages) (
                        AllocateMaxAddress,
                        EfiReservedMemoryType,
                        EFI_SIZE_TO_PAGES (4*1024),
                        &gTbtDgpuPciBaseAddressMem
                        );
       ASSERT_EFI_ERROR(Status);

      //
      //Initialize with '0xFF'
      //
        SetMem ((VOID *)(UINTN)gTbtDgpuPciBaseAddressMem, 4*1024, 0xFF);
        mGlobalNvsAreaPtr->DGfxPciBaseAddressMem = (UINT32) gTbtDgpuPciBaseAddressMem;

        mSetupData.TbtDgpuPciBaseAddressMem = (UINT32) gTbtDgpuPciBaseAddressMem;
        DEBUG ((DEBUG_INFO, "<TbtDXE> TbtDgpuPciBaseAddressMem = %lx \n", gTbtDgpuPciBaseAddressMem));

      //
      // Tbt Device Tree buffer
      //

      Status = (gBS->AllocatePool) (
                          EfiReservedMemoryType,
                          256,
                          &MemoryBuffer
                          );
      ASSERT_EFI_ERROR(Status);

      gTbtDeviceTreeBuffer = (EFI_PHYSICAL_ADDRESS) MemoryBuffer;
      //
      //Initialize with '0x00'
      //
      SetMem ((VOID *)(UINTN)gTbtDeviceTreeBuffer, 256, 0x00);

      mGlobalNvsAreaPtr->DGfxTbtDeviceTreeBuffer = (UINT32) gTbtDeviceTreeBuffer;

      mSetupData.TbtDeviceTreeBuffer = (UINT32) gTbtDeviceTreeBuffer;
      DEBUG ((DEBUG_INFO, "<TbtDXE> TbtDeviceTreeBuffer = %lx \n", gTbtDeviceTreeBuffer));
    }
    Status = gRT->SetVariable (
                    L"Setup",
                    &gSetupVariableGuid,
                    SetupAttr,
                    DataSize,
                    &mSetupData
                    );
    ASSERT_EFI_ERROR(Status);
  }

  Status = EfiCreateEventReadyToBootEx (
            TPL_CALLBACK,
            TbtCallback,
            NULL,
            &Event
            );

  DataSize = sizeof (SA_SETUP);
  Status = gRT->GetVariable (
                  L"SaSetup",
                  &gSaSetupVariableGuid,
                  NULL,
                  &DataSize,
                  &SaSetup
                  );

  if (!EFI_ERROR (Status) && SaSetup.EnableVtd == FALSE) {
    //
    // Register an End of DXE event for extended a TPM log to PCR[7]
    //
    Status = gBS->CreateEventEx (
                    EVT_NOTIFY_SIGNAL,
                    TPL_CALLBACK,
                    TbtSecurityEndOfDxeCallBackFunction,
                    NULL,
                    &gEfiEndOfDxeEventGroupGuid,
                    &EndOfDxeEvent
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Failed to egister an End of DXE event for extended a TPM log to PCR[7], Status: %d\n", Status));
      gBS->CloseEvent (EndOfDxeEvent);
      goto Exit;
    }
  }

  if (mSetupData.TbtVtdBaseSecurity == TRUE) {
    //
    // Register a Ready to Lock event for set DMAR Flag
    //
    Status = gBS->CreateEventEx (
                    EVT_NOTIFY_SIGNAL,
                    TPL_NOTIFY,
                    TbtSecurityReadyToLockCallBackFunction,
                    NULL,
                    &gEfiEndOfDxe2EventGuid,
                    &ReadyToLockEvent
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Failed to Register a Ready to Lock event for set DMAR Flag, Status: %d\n", Status));
      gBS->CloseEvent (ReadyToLockEvent);
      goto Exit;
    }

    //
    // Register a Exit Boot Service for disable TBT BME
    //
    Status = gBS->CreateEventEx (
                    EVT_NOTIFY_SIGNAL,
                    TPL_NOTIFY,
                    TbtSecurityExitBootCallBackFunction,
                    NULL,
                    &gEfiEventExitBootServicesGuid,
                    &ExitBootServiceEvent
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Failed to Register a Exit Boot Service for disable TBT BME, Status: %d\n", Status));
      gBS->CloseEvent (ExitBootServiceEvent);
      goto Exit;
    }

    //
    // Install TBT DisableBme for Shell testing purpose
    //
    InstallDisableBmeProtocol ();
  }
Exit:
  return Status;
}
